/*
* EASYDROPDOWN - A Drop-down Builder for Styleable Inputs and Menus
* Version: 2.1.4
* License: Creative Commons Attribution 3.0 Unported - CC BY 3.0
* http://creativecommons.org/licenses/by/3.0/
* This software may be used freely on commercial and non-commercial projects with attribution to the author/copyright holder.
* Author: Patrick Kunka
* Copyright 2013 Patrick Kunka, All Rights Reserved
*/


(function($){
	
	function EasyDropDown(){
		this.isField = true,
		this.down = false,
		this.inFocus = false,
		this.disabled = false,
		this.cutOff = false,
		this.hasLabel = false,
		this.keyboardMode = false,
		this.nativeTouch = true,
		this.wrapperClass = 'dropdown',
		this.onChange = null;
	};
	
	EasyDropDown.prototype = {
		constructor: EasyDropDown,
		instances: {},
		init: function(domNode, settings){
			var	self = this;
			
			$.extend(self, settings);
			self.$select = $(domNode);
			self.id = domNode.id;
			self.options = [];
			self.$options = self.$select.find('option');
			self.isTouch = 'ontouchend' in document;
			self.$select.removeClass(self.wrapperClass+' dropdown');
			if(self.$select.is(':disabled')){
				self.disabled = true;
			};
			if(self.$options.length){
				self.$options.each(function(i){
					var $option = $(this);
					if($option.is(':selected')){
						self.selected = {
							index: i,
							title: $option.text()
						}
						self.focusIndex = i;
					};
					if($option.hasClass('label') && i == 0){
						self.hasLabel = true;
						self.label = $option.text();
						$option.attr('value','');
					} else {
						self.options.push({
							domNode: $option[0],
							title: $option.text(),
							value: $option.val(),
							selected: $option.is(':selected')
						});
					};
				});
				if(!self.selected){
					self.selected = {
						index: 0,
						title: self.$options.eq(0).text()
					}
					self.focusIndex = 0;
				};
				self.render();
			};
		},
	
		render: function(){
			var	self = this,
				touchClass = self.isTouch && self.nativeTouch ? ' touch' : '',
				disabledClass = self.disabled ? ' disabled' : '';
			
			self.$container = self.$select.wrap('<div class="'+self.wrapperClass+touchClass+disabledClass+'"><span class="old"/></div>').parent().parent();
			self.$active = $('<span class="selected">'+self.selected.title+'</span>').appendTo(self.$container);
			self.$carat = $('<span class="carat"/>').appendTo(self.$container);
			self.$scrollWrapper = $('<div><ul/></div>').appendTo(self.$container);
			self.$dropDown = self.$scrollWrapper.find('ul');
			self.$form = self.$container.closest('form');
			$.each(self.options, function(){
				var	option = this,
					active = option.selected ? ' class="active"':'';
				self.$dropDown.append('<li'+active+'>'+option.title+'</li>');
			});
			self.$items = self.$dropDown.find('li');
			
			if(self.cutOff && self.$items.length > self.cutOff)self.$container.addClass('scrollable');
			
			self.getMaxHeight();
	
			if(self.isTouch && self.nativeTouch){
				self.bindTouchHandlers();
			} else {
				self.bindHandlers();
			};
		},
		
		getMaxHeight: function(){
			var self = this;
			
			self.maxHeight = 0;
			
			for(i = 0; i < self.$items.length; i++){
				var $item = self.$items.eq(i);
				self.maxHeight += $item.outerHeight();
				if(self.cutOff == i+1){
					break;
				};
			};
		},
		
		bindTouchHandlers: function(){
			var	self = this;
			self.$container.on('click.easyDropDown',function(){
				self.$select.focus();
			});
			self.$select.on({
				change: function(){
					var	$selected = $(this).find('option:selected'),
						title = $selected.text(),
						value = $selected.val();
						
					self.$active.text(title);
					if(typeof self.onChange === 'function'){
						self.onChange.call(self.$select[0],{
							title: title, 
							value: value
						});
					};
				},
				focus: function(){
					self.$container.addClass('focus');
				},
				blur: function(){
					self.$container.removeClass('focus');
				}
			});
		},
	
		bindHandlers: function(){
			var	self = this;
			self.query = '';
			self.$container.on({
				'click.easyDropDown': function(){
					if(!self.down && !self.disabled){
						self.open();
					} else {
						self.close();
					};
				},
				'mousemove.easyDropDown': function(){
					if(self.keyboardMode){
						self.keyboardMode = false;
					};
				}
			});
			
			$('body').on('click.easyDropDown.'+self.id,function(e){
				var $target = $(e.target),
					classNames = self.wrapperClass.split(' ').join('.');

				if(!$target.closest('.'+classNames).length && self.down){
					self.close();
				};
			});

			self.$items.on({
				'click.easyDropDown': function(){
					var index = $(this).index();
					self.select(index);
					self.$select.focus();
				},
				'mouseover.easyDropDown': function(){
					if(!self.keyboardMode){
						var $t = $(this);
						$t.addClass('focus').siblings().removeClass('focus');
						self.focusIndex = $t.index();
					};
				},
				'mouseout.easyDropDown': function(){
					if(!self.keyboardMode){
						$(this).removeClass('focus');
					};
				}
			});

			self.$select.on({
				'focus.easyDropDown': function(){
					self.$container.addClass('focus');
					self.inFocus = true;
				},
				'blur.easyDropDown': function(){
					self.$container.removeClass('focus');
					self.inFocus = false;
				},
				'keydown.easyDropDown': function(e){
					if(self.inFocus){
						self.keyboardMode = true;
						var key = e.keyCode;

						if(key == 38 || key == 40 || key == 32){
							e.preventDefault();
							if(key == 38){
								self.focusIndex--
								self.focusIndex = self.focusIndex < 0 ? self.$items.length - 1 : self.focusIndex;
							} else if(key == 40){
								self.focusIndex++
								self.focusIndex = self.focusIndex > self.$items.length - 1 ? 0 : self.focusIndex;
							};
							if(!self.down){
								self.open();
							};
							self.$items.removeClass('focus').eq(self.focusIndex).addClass('focus');
							if(self.cutOff){
								self.scrollToView();
							};
							self.query = '';
						};
						if(self.down){
							if(key == 9 || key == 27){
								self.close();
							} else if(key == 13){
								e.preventDefault();
								self.select(self.focusIndex);
								self.close();
								return false;
							} else if(key == 8){
								e.preventDefault();
								self.query = self.query.slice(0,-1);
								self.search();
								clearTimeout(self.resetQuery);
								return false;
							} else if(key != 38 && key != 40){
								var letter = String.fromCharCode(key);
								self.query += letter;
								self.search();
								clearTimeout(self.resetQuery);
							};
						};
					};
				},
				'keyup.easyDropDown': function(){
					self.resetQuery = setTimeout(function(){
						self.query = '';
					},1200);
				}
			});
			
			self.$dropDown.on('scroll.easyDropDown',function(e){
				if(self.$dropDown[0].scrollTop >= self.$dropDown[0].scrollHeight - self.maxHeight){
					self.$container.addClass('bottom');
				} else {
					self.$container.removeClass('bottom');
				};
			});
			
			if(self.$form.length){
				self.$form.on('reset.easyDropDown', function(){
					var active = self.hasLabel ? self.label : self.options[0].title;
					self.$active.text(active);
				});
			};
		},
		
		unbindHandlers: function(){
			var self = this;
			
			self.$container
				.add(self.$select)
				.add(self.$items)
				.add(self.$form)
				.add(self.$dropDown)
				.off('.easyDropDown');
			$('body').off('.'+self.id);
		},
		
		open: function(){
			var self = this,
				scrollTop = window.scrollY || document.documentElement.scrollTop,
				scrollLeft = window.scrollX || document.documentElement.scrollLeft,
				scrollOffset = self.notInViewport(scrollTop);

			self.closeAll();
			self.getMaxHeight();
			self.$select.focus();
			window.scrollTo(scrollLeft, scrollTop+scrollOffset);
			self.$container.addClass('open');
			self.$scrollWrapper.css('height',self.maxHeight+'px');
			self.down = true;
		},
		
		close: function(){
			var self = this;
			self.$container.removeClass('open');
			self.$scrollWrapper.css('height','0px');
			self.focusIndex = self.selected.index;
			self.query = '';
			self.down = false;
		},
		
		closeAll: function(){
			var self = this,
				instances = Object.getPrototypeOf(self).instances;
			for(var key in instances){
				var instance = instances[key];
				instance.close();
			};
		},
	
		select: function(index){
			var self = this;
			
			if(typeof index === 'string'){
				index = self.$select.find('option[value='+index+']').index() - 1;
			};
			
			var	option = self.options[index],
				selectIndex = self.hasLabel ? index + 1 : index;
			self.$items.removeClass('active').eq(index).addClass('active');
			self.$active.text(option.title);
			self.$select
				.find('option')
				.removeAttr('selected')
				.eq(selectIndex)
				.prop('selected',true)
				.parent()
				.trigger('change');
				
			self.selected = {
				index: index,
				title: option.title
			};
			self.focusIndex = i;
			if(typeof self.onChange === 'function'){
				self.onChange.call(self.$select[0],{
					title: option.title, 
					value: option.value
				});
			};
		},
		
		search: function(){
			var self = this,
				lock = function(i){
					self.focusIndex = i;
					self.$items.removeClass('focus').eq(self.focusIndex).addClass('focus');
					self.scrollToView();	
				},
				getTitle = function(i){
					return self.options[i].title.toUpperCase();
				};
				
			for(i = 0; i < self.options.length; i++){
				var title = getTitle(i);
				if(title.indexOf(self.query) == 0){
					lock(i);
					return;
				};
			};
			
			for(i = 0; i < self.options.length; i++){
				var title = getTitle(i);
				if(title.indexOf(self.query) > -1){
					lock(i);
					break;
				};
			};
		},
		
		scrollToView: function(){
			var self = this;
			if(self.focusIndex >= self.cutOff){
				var $focusItem = self.$items.eq(self.focusIndex),
					scroll = ($focusItem.outerHeight() * (self.focusIndex + 1)) - self.maxHeight;
			
				self.$dropDown.scrollTop(scroll);
			};
		},
		
		notInViewport: function(scrollTop){
			var self = this,
				range = {
					min: scrollTop,
					max: scrollTop + (window.innerHeight || document.documentElement.clientHeight)
				},
				menuBottom = self.$dropDown.offset().top + self.maxHeight;
				
			if(menuBottom >= range.min && menuBottom <= range.max){
				return 0;
			} else {
				return (menuBottom - range.max) + 5;
			};
		},
		
		destroy: function(){
			var self = this;
			self.unbindHandlers();
			self.$select.unwrap().siblings().remove();
			self.$select.unwrap();
			delete Object.getPrototypeOf(self).instances[self.$select[0].id];
		},
		
		disable: function(){
			var self = this;
			self.disabled = true;
			self.$container.addClass('disabled');
			self.$select.attr('disabled',true);
			if(!self.down)self.close();
		},
		
		enable: function(){
			var self = this;
			self.disabled = false;
			self.$container.removeClass('disabled');
			self.$select.attr('disabled',false);
		}
	};
	
	var instantiate = function(domNode, settings){
			domNode.id = !domNode.id ? 'EasyDropDown'+rand() : domNode.id;
			var instance = new EasyDropDown();
			if(!instance.instances[domNode.id]){
				instance.instances[domNode.id] = instance;
				instance.init(domNode, settings);
			};
		},
		rand = function(){
			return ('00000'+(Math.random()*16777216<<0).toString(16)).substr(-6).toUpperCase();
		};
	
	$.fn.easyDropDown = function(){
		var args = arguments,
			dataReturn = [],
			eachReturn;
			
		eachReturn = this.each(function(){
			if(args && typeof args[0] === 'string'){
				var data = EasyDropDown.prototype.instances[this.id][args[0]](args[1], args[2]);
				if(data)dataReturn.push(data);
			} else {
				instantiate(this, args[0]);
			};
		});
		
		if(dataReturn.length){
			return dataReturn.length > 1 ? dataReturn : dataReturn[0];
		} else {
			return eachReturn;
		};
	};
	
	$(function(){
		if(typeof Object.getPrototypeOf !== 'function'){
			if(typeof 'test'.__proto__ === 'object'){
				Object.getPrototypeOf = function(object){
					return object.__proto__;
				};
			} else {
				Object.getPrototypeOf = function(object){
					return object.constructor.prototype;
				};
			};
		};
		
		$('select.dropdown').each(function(){
			var json = $(this).attr('data-settings');
				settings = json ? $.parseJSON(json) : {}; 
			instantiate(this, settings);
		});
	});
})(jQuery);